package youwen.tcpclient;

import java.io.*;
import java.net.*;
import youwen.jiekou.TCPClientcallBack;
import youwen.tool.RegularJudgment;

/**
 * 该类实现TCP客户端各操作
 * 
 * 开心跳后
 * 1.心跳发送没有回复3次会断
 * 2.发送心跳失败会断
 * 正常检查断的情况
 * 3.发送消息到服务器失败会断
 * 4.发送消息编码失败会断
 * 5.接收消息内容长度小于等于0会断
 * 6.接收消息异常会断
 * 7.本地连接的一些状态检查,不符合正常情况断开,5秒检查一次
 * @author KK
 *
 */
public final class TCPclient {

	private static InputStream inputStream; // 从TCP来的输入流
	private static OutputStream outputStream;// 向TCP输出的输出流
	private boolean bConnected = false;// 连接标识

	public boolean isbConnected() {
		return bConnected;
	}

	private boolean LisTh = true;// 控制监听线程运行
	private boolean ChTh = true;// 控制重连线程运行
	private Socket TCPSocket;// TCP连接套接字

	public Socket getTCPSocket() {
		return TCPSocket;
	}

	private String Hreatdata = "F";// 心跳数据
	private boolean Ishreat = false;// 是否开启心跳
	private long HreatTime = 60;// 心跳间隔时间，单位s
	private boolean IsReCon = false;// 是否需要重连
	private long ReconnectionTime = 30;// 重连时间，单位s

	private TCPClientcallBack TCPClientcallBack = null;
	private String ServerIP = null;
	private int ServerPort = 0;
	private boolean Isstartscces = false;// 第一次是否初始化成功
	private byte Hreatnum = 0;// 心跳发送没有收到回复次数
	private byte HreatMaxnum = 3;// 心跳发送没有收到回复次数超过该值就认为tcp连续断开,默认3次都没有回复就认为通讯断开
	private byte MaxNumSate = 5;//多久检查一次当前连接的本地状态,单位s,默认5秒
	private boolean IsLegi = false;//IP和端口是否合法,不合法都不开启重连/心跳线程
	/**
	 * TCP初始化，初始化成功返回1，失败返回0
	 * 
	 * @param IP
	 *            待连接服务器IP
	 * @param Port
	 *            待连接服务器端口
	 * @param clallback
	 *            回调对象
	 * @return 0表示连接成功，-1表示地址错误，-2表示连接失败
	 */
	public byte startTCPclient(String IP, int Port, TCPClientcallBack clallback) {
		if(RegularJudgment.isboolIp(IP) && Port>0) {
			IsLegi = true;
			try {
				ServerIP = IP;
				ServerPort = Port;
				TCPClientcallBack = clallback;
				TCPSocket = new Socket(IP, Port);// 创建连接字
				try {
					TCPSocket.setKeepAlive(true);// 开启tcp连接保活
				} catch (SocketException e1) {
					// TODO 自动生成的 catch 块
					e1.printStackTrace();
				}
				inputStream = new DataInputStream(TCPSocket.getInputStream());// 输入流
				outputStream = new DataOutputStream(TCPSocket.getOutputStream());// 输出流
				LisenDataTread T = new LisenDataTread();
				LisTh = true;
				T.start();
				bConnected = true;
				Isstartscces = true;
				TCPClientcallBack.Hasconnect();// 回调执行连接上了
				System.out.println("TCP connect scccess!!");
				return 0;
			} catch (UnknownHostException e) {
				bConnected = false;
				TCPClientcallBack.ConnectErro();
				// TODO 自动生成的 catch 块
				e.printStackTrace();
				return -1;
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
				bConnected = false;
				TCPClientcallBack.ConnectErro();
				return -2;
			}
		}
		return -1;
	}

	/**
	 * 开启重连
	 * 
	 * @param time
	 *            重连时间间隔，单位s
	 */
	public void StartReConncet(long time) {
		if(IsLegi) {
			IsReCon = true;
			ReconnectionTime = time;
			if (Ishreat == false) {
				Thread T = new MyHreat();
				T.start();
			}
		}
		
	}

	/**
	 * 开启心跳
	 * 
	 * @param HreatMSG
	 * @param time
	 *            心跳间隔时间，单位s
	 */
	public void StartHreat(String HreatMSG, long time) {
		if(IsLegi) {
			Ishreat = true;
			HreatTime = time;
			Hreatdata = HreatMSG;
			if (IsReCon == false) {
				Thread T = new MyHreat();
				T.start();
			}
		}
	}

	
	/**
	 * 销毁本地连接,一般在销毁客户端对象或连接是时调用
	 */
	private void destorySocket() {
		bConnected = false;
		try {
			inputStream.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		try {
			outputStream.close();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		try {
			TCPSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 销毁和停止
	 */
	public void destroyobj() {
		LisTh = false;
		ChTh = false;
		destorySocket();
		bConnected = false;
	}

	/**
	 * tcp连接断开
	 */
	private void Disconnected() {
		if(bConnected) {
			destorySocket();
			TCPClientcallBack.Disconnected();//连接断开，执行回调
		}
	}
	
	/**
	 * tcp客户端发送数据
	 * 
	 * @param msg
	 *            待发送的字符串
	 */
	public byte TCPClientSend(String msg) {
		byte[] sendbuffer;
		byte rs = -2;
		try {
			sendbuffer = msg.getBytes("utf-8");
			rs = TCPClientsendbyteData(sendbuffer, 0, sendbuffer.length);
		} catch (UnsupportedEncodingException e) {
			// TODO 自动生成的 catch 块
			Disconnected();
			e.printStackTrace();
			rs = -1;
		}
		return rs;
	}

	/**
	 * 
	 * @param SendBuff
	 *            待发送数据帧数组
	 * @param off
	 *            数组起始位置
	 * @param length
	 *            要发送数据个数
	 * @return 0表示发送成功，-1表示还没连接，-2表示发送失败
	 */
	public byte TCPClientsendbyteData(byte[] SendBuff, int off, int length) {
		try {
			if (bConnected) {
				if(TCPSocketState()) {
					outputStream.write(SendBuff, off, length);
					outputStream.flush();// 输出到缓冲区
					return 0;
				}else {
					Disconnected();//如果本地状态失效则连接已经断开了
					return -1;
				}
			} else {
				return -1;
			}
		} catch (IOException e) {
			Disconnected();
			return -2;
		} // 把数据发送出去
	}

	/**
	 * 根据TCPSocket的各种状态返回本地连接的状态
	 * 
	 * @return
	 */
	private boolean TCPSocketState() {
		return TCPSocket.isConnected() && !TCPSocket.isClosed() && TCPSocket.isBound() && !TCPSocket.isInputShutdown()
				&& !TCPSocket.isOutputShutdown();
	}

	/**
	 * 开辟一个线程监视TCP数据
	 * 
	 * @author zhao
	 */
	private class LisenDataTread extends Thread {
		public void run() {
			int rcnum = -1;// 防止断开后多次回调报断开
			while (LisTh) {
				if (TCPSocketState() && bConnected) {
					rcnum = -1;
					int numBytes = -1;
					try {
						int  datanum = inputStream.available();
						if(datanum>0) {
							byte readbuff[] = new byte[datanum];
							numBytes = inputStream.read(readbuff);// 获取TCP输入流数据帧放入数据readbuff里
							if (numBytes <= 0) {
								Disconnected();// 读取到数据长度小于1则已经断开了
							} else {
								boolean istrue = false;// 过滤发送过来的数据是0x0ascci的错误情况
								for (int k = 0; k < 8 && k < numBytes; k++) {
									if (readbuff[k] != 0) {
										istrue = true;
									}
								}
								if (istrue) {
									if (Ishreat && numBytes == 1) {//心跳开启才检查是否是心跳的回复
										String rstr = new String(readbuff, 0, numBytes, "utf-8");
										if (rstr.equals(Hreatdata)) {
											bConnected = true;// 有心跳回复说明连接正常
											Hreatnum = 0;
										} else {
											byte readbufft[] = new byte[numBytes];
											System.arraycopy(readbuff, 0, readbufft, 0, numBytes);
											TCPClientcallBack.MssageArrived(readbufft, numBytes);
										}
									} else {
										byte readbufft[] = new byte[numBytes];
										System.arraycopy(readbuff, 0, readbufft, 0, numBytes);
										TCPClientcallBack.MssageArrived(readbufft, numBytes);
									}
								}
							}
						}
					} catch (IOException e) {
						Disconnected();// 读取到数据长度小于1则已经断开了
					}
					try {
						Thread.sleep(1);//在没有连接状态下减低CPU使用率
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
				} else {
					if (rcnum == -1) {//防止多次报通讯失败
						Disconnected();// 读取到数据长度小于1则已经断开了
						rcnum = 0;
					}
					try {
						Thread.sleep(10);//在没有连接状态下减低CPU使用率
					} catch (InterruptedException e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
					}
				}
				
			}
		}
	}

	private class MyHreat extends Thread {
		public void run() {
			long RCnum = 0;//重连时间计时变量
			long Hrnum = 0;//心跳时间计时变量
			long Statenum = 0;//本地连接状态检查
			while (ChTh) {
				try {
					if (bConnected) {
						if (Ishreat) {
							Hrnum++;
							if(Hrnum>= HreatTime*100) {
								try {
									Hrnum = 0;
									byte[] sendbuffer = Hreatdata.getBytes("utf-8");
									outputStream.write(sendbuffer);
									outputStream.flush();// 输出到缓冲区
									Hreatnum++;
									if (Hreatnum > HreatMaxnum) {
										Disconnected();// 读取到数据长度小于1则已经断开了
									}
								} catch (IOException e1) {
									Disconnected();// 读取到数据长度小于1则已经断开了
								}
							}
						}
						Statenum++;
						if(Statenum>= MaxNumSate*100) {
							Statenum = 0;
							if(!TCPSocketState()) {
								Disconnected();//本地连接状态检查到断开就断开
							}
						}
					}
					else if (!bConnected && IsReCon) {
						RCnum++;
						if (RCnum >= ReconnectionTime*100) {
							RCnum = 0;
							try {
								Hreatnum = 0;// 重连把心跳发送次数复位
								TCPSocket = new Socket(ServerIP, ServerPort);
								inputStream = new DataInputStream(TCPSocket.getInputStream());// 输入流
								outputStream = new DataOutputStream(TCPSocket.getOutputStream());// 输出流
								bConnected = true;
								if (!Isstartscces) {
									LisenDataTread T = new LisenDataTread();
									T.start();
									Isstartscces = true;
								}
								TCPClientcallBack.Hasconnect();// 回调执行连接上了
							} catch (IOException e) {
								bConnected = false;
								TCPClientcallBack.ConnectErro();
								// TODO 自动生成的 catch 块
							}
						}
					}
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
	}
}
